<?php

namespace pmvc\annotation;

use pmvc\util\StringUtil;
use pmvc\io\IOUtil;
use pmvc\util\ClassUtil;

use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use ReflectionFunction;
use ReflectionObject;

/**
 * Utilities for reading annotations.  This is a convenience
 * class so that code reading annotations don't have to create
 * and keep around an {@link AnnotationReader} instance.
 *
 */
class AnnotationUtil {

	private static $ANNOTATION_READER;

	/**
	 * Sets the {@link AnnotationReader} that this utility
	 * class uses to read {@link Annotation}s.
	 * 
	 * @param AnnotationReader $annotationReader
	 */
	public static function setAnnotationReader(AnnotationReader $annotationReader) {
		self::$ANNOTATION_READER = $annotationReader;
	}

	/**
	 * Returns the {@link AnnotationReader} that this utility
	 * class uses to read {@link Annotation}s. By default this
	 * utility uses a {@link CachedAnnotationReaderImpl} 
	 * but it can be overriden using {@code setAnnotationReader}.
	 * 
	 * @return AnnotationReader
	 */
	public static function getAnnotationReader() {
		if (!isset(self::$ANNOTATION_READER)) {
			$annotationReader = new AnnotationReaderImpl();
			$annotationReader->setStrict(true);
			self::$ANNOTATION_READER =  new CachedAnnotationReaderImpl($annotationReader);
		}
		return self::$ANNOTATION_READER;
	}

	/**
	 * Returns {@link ReflectionMethod}s on the given class that
	 * have at least one of the given annotations.
	 * @param array|ReflectionClass $class
	 * @param array $types
	 * @return array of ReflectionMethod
	 */
	public static function findMethodsWithAnnotations($class, Array $types=Array()) {
		// get ReflectionClass
		if (is_string($class)) {
			$class = new ReflectionClass($class);
		}

		// read each method
		$ret = Array();
		foreach ($class->getMethods() as $method) {
			$annots = self::getMethodAnnotations($method, $types);
			if (count($annots)>0) {
				$ret []= $method;
			}
		}

		// return the methods
		return $ret;
	}

	/**
	 * Returns {@link ReflectionMethod}s on the given class that
	 * have at the given annotation
	 * @param array|ReflectionClass $class
	 * @param string $type
	 * @return array of ReflectionMethod
	 */
	public static function findMethodsWithAnnotation($class, $type) {
		return self::findMethodsWithAnnotations($class, Array($type));
	}

	/**
	 * Returns {@link ReflectionProperty}s on the given class that
	 * have at least one of the given annotations.
	 * @param array|ReflectionClass $class
	 * @param array $types
	 * @return array of ReflectionProperty
	 */
	public static function findPropertiesWithAnnotations($class, Array $types=Array()) {
		// get ReflectionClass
		if (is_string($class)) {
			$class = new ReflectionClass($class);
		}

		// read each method
		$ret = Array();
		foreach ($class->getProperties() as $property) {
			$annots = self::getPropertyAnnotations($method, $types);
			if (count($annots)>0) {
				$ret []= $property;
			}
		}

		// return the methods
		return $ret;
	}

	/**
	 * Returns {@link ReflectionProperty}s on the given class that
	 * have at least one of the given annotations.
	 * @param array|ReflectionClass $class
	 * @param array $types
	 * @return array of ReflectionProperty
	 */
	public static function findPropertiesWithAnnotation($class, $type) {
		return self::findPropertiesWithAnnotations($class, Array($type));
	}

	/**
	 * Reads class level annotations optionally limmiting it
	 * to return the specified types.
	 * 
	 * @see AnnotationReader
	 * @param string|ReflectionClass $class the class
	 * @param string|array $types types to return
	 * @return array of \pmvc\annotations\Annotation
	 */
	public static function getClassAnnotations($class, $types=Array()) {
		if (isset($types) && !is_array($types)) {
			$types = Array($types);
		}
		return self::getAnnotationReader()->readClassAnnotations($class, $types);
	}

	/**
	 * Reads method level annotations optionally limmiting it
	 * to return the specified types.  The {@code $method} parameter
	 * can be a {@link ReflectionMethod} or an array in the
	 * following form:
	 * <pre>
	 * Array('\namepsace\ClassName', 'methodName')
	 * </pre>
	 *
	 * @see AnnotationReader
	 * @param array|ReflectionMethod $method the method
	 * @param string|array $types the types to return
	 * @return array of \pmvc\annotations\Annotation
	 */
	public static function getMethodAnnotations($method, $types=Array()) {
		if (isset($types) && !is_array($types)) {
			$types = Array($types);
		}
		return self::getAnnotationReader()->readMethodAnnotations($method, $types);
	}

	/**
	 * Reads property level annotations optionally limmiting it
	 * to return the specified types.  The {@code $property} parameter
	 * can be a {@link ReflectionProperty} or an array in the
	 * following form:
	 * <pre>
	 * Array('\namepsace\ClassName', 'propertyName')
	 * </pre>
	 *
	 * @see AnnotationReader
	 * @param array|ReflectionProperty $property the property
	 * @param string|array $types the types to return
	 * @return array of \pmvc\annotations\Annotation
	 */
	public static function getPropertyAnnotations($property, $types=Array()) {
		if (isset($types) && !is_array($types)) {
			$types = Array($types);
		}
		return self::getAnnotationReader()->readPropertyAnnotations($property, $types);
	}

	/**
	 * Reads function level annotations optionally limmiting it
	 * to return the specified types.
	 * 
	 * @see AnnotationReader
	 * @param string|callback $function the function
	 * @param array $types the types to return
	 * @return array of \pmvc\annotations\Annotation
	 */
	public static function getFunctionAnnotations($function, Array $types=Array()) {
		if (isset($types) && !is_array($types)) {
			$types = Array($types);
		}
		return self::getAnnotationReader()->readFunctionAnnotations($function, $types);
	}

	/**
	 * Determines the type of object passed and delegates to
	 * one of the other {@code get*Annotations} methods.
	 * @param mixed $object the object to read annotations from
	 * @param array $types the type names of the annotations to read
	 * @return array of {@link Annotation}s
	 */
	public static function getAnnotations($object, Array $types=Array()) {
		if ($object instanceof \ReflectionClass) {
			return self::getReflectionClassAnnotations($object, $types);
			
		} else if ($object instanceof \ReflectionMethod) {
			return self::getReflectionMethodAnnotations($object, $types);
			
		} else if ($object instanceof \ReflectionProperty) {
			return self::getReflectionPropertyAnnotations($object, $types);
			
		} else if ($object instanceof \ReflectionFunction) {
			return self::getReflectionFunctionAnnotations($object, $types);
			
		} else if (is_object($object)) {
			return self::getReflectionClassAnnotations(new ReflectionObject($object), $types);
		}
		throw new ReflectionException(
			"Unable to determine object type for annotation reflection");
	}

	/**
	 * Returns annotations defined on a {@link ReflectionClass}.
	 * @param ReflectionClass $obj
	 * @param array $types the type names of the annotations to read
	 * @return array of {@link Annotation}s
	 */
	public static function getReflectionClassAnnotations(ReflectionClass $obj, Array $types=Array()) {
		return self::getAnnotationReader()->readClassAnnotations($obj, $types);
	}

	/**
	 * Returns annotations defined on a {@link ReflectionMethod}.
	 * @param ReflectionMethod $obj
	 * @param array $types the type names of the annotations to read
	 * @return array of {@link Annotation}s
	 */
	public static function getReflectionMethodAnnotations(ReflectionMethod $obj, Array $types=Array()) {
		return self::getAnnotationReader()->readMethodAnnotations($obj, $types);
	}

	/**
	 * Returns annotations defined on a {@link ReflectionProperty}.
	 * @param ReflectionProperty $obj
	 * @param array $types the type names of the annotations to read
	 * @return array of {@link Annotation}s
	 */
	public static function getReflectionPropertyAnnotations(ReflectionProperty $obj, Array $types=Array()) {
		return self::getAnnotationReader()->readPropertyAnnotations($obj, $types);
	}

	/**
	 * Returns annotations defined on a {@link ReflectionFunction}.
	 * @param ReflectionFunction $obj
	 * @param array $types the type names of the annotations to read
	 * @return array of {@link Annotation}s
	 */
	public static function getReflectionFunctionAnnotations(ReflectionFunction $obj, Array $types=Array()) {
		return self::getAnnotationReader()->readFunctionAnnotations($obj, $types);
	}

}

?>